/*
 * @LogFactory.java	2008/07/28
 *
 * Copyright 2008 Web & Zen Multimedia, All rights reserved.
 * Use is subject to license terms.
 */
package it.wzm.lightweightlog;


import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.net.URL;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Properties;
import java.util.regex.Pattern;

/**
 * LogFactory is the accessor for getting all instances of Queue, QueueDumper and Register.
 * The idea is to implement Factory Pattern: only one instance of each Objects, and a Factory which initialize and serve all of them.<br>
 * The initialization is done when the method <I>LogFactory.getLog()</I> is called.<br>
 * If there is already a valid log then it is returned, otherwise the factory will initialize each elements according to the <I>dumper.properties</I> file.<br>
 * If no properties are found on the classpath, then a default initialization is made. Wich is a System.out with INFO as severity and a DumpedQueue.<br/>
 * The step for initialization are:
 * <ul>
 *    <li>a property file, dump.properties is searched in the classpath then if not found the defaul one inside package is taken</li>
 *    <li>Acquire properties from file</li>
 *    <li>Initialize the Log</li>
 *    <li>Initialize the Dumpers</li>
 *    <li>Initialize the Register</li>
 *    <li>Initialize the Queue</li>
 *    <li>Initialize and start QueueDumper (if necessary)</li>
 *    <li>Listen to log request...</li>
 * </ul>
 * 
 * If any of this point fail to initialize, then a LightweightlogInitializationException is raisen
 * and a default construction is made.
 * @author Leonardo Celati
 */
public final class LogFactory implements Constant{
    
    /**
     * Represents status of the Log
     * @author Leonardo Celati
     */
    public enum Status {
        /**
         * Initialization mode
         */
        INIT,
        /**
         * Running mode
         */
        RUNNING,
        /**
         * Shutdown mode
         */
        SHUTDOWN,
    }
    /**
     * Status of LogFactory
     */
    private static Status status;    
    /**
     * The Queue holder
     */
    private static Queue<? extends Entry> q;
    /**
     * The Register holder
     */
    private static Register<? extends Dumper> r;
    /**
     * The QueueDumper holder
     */
    private static QueueDumper qd;
    /**
     * Log holder
     */
    private static Log l;
    /**
     * Dumper holder
     */
    private static ArrayList<Dumper> dumpers;
    /**
     * The current status of this Logger
     * @return the status
     */
    public static Status getStatus() {
        return status;
    }    
    
    /**
     * Accessor for log, it will look for dump.properties in classpath
     * @return valid instance of Log
     */
    public static Log getLog() {
        
        // we didn't build yet a Log
        // so it is constructed and init() called
        if (l == null) {        	
        	init(LogFactory.class.getClassLoader().getResourceAsStream(PROP_NAME));        	
        }
        return l;
    }  
    
    /**
     * Accessor for log, it will look for a specific properties file in url
     * @param url a valid point to dump.properties
     * @return valid Log
     */
    public static Log getLog(URL url) {
        
        // we didn't build yet a Log
        // so it is constructed and init() called
        if (l == null) {       	
        	try {
        		init(url.openStream());
        	} catch (IOException e) {
        		init(null);
        	}
        }
        return l;
    }
    
    /**
     * Accessor for log, it will look for a specific properties file in InputStream
     * @param is a valid point to dump.properties
     * @return valid Log
     */    
    public static Log getLog(InputStream is) {
        
        // we didn't build yet a Log
        // so it is constructed and init() called
        if (l == null) {
            init(is);
        }
        return l;
    }

    /**
     * Accessor for log, it will look for a specific properties file
     * @param f a valid point to dump.properties
     * @return valid Log
     */    
    public static Log getLog(File f) {
        
        // we didn't build yet a Log
        // so it is constructed and init() called
        if (l == null) {       	
        	try {
        		init(new FileInputStream(f));
        	} catch (IOException e) {
        		init(null);
        	}
        }
        return l;
    }

    /**
     * Accessor for log, it will look for a specific file in classpath, otherwise if absolute to a file
     * @param url a valid point to dump.properties
     * @return valid Log
     */
    public static Log getLog(String filename) {
        
        // we didn't build yet a Log
        // so it is constructed and init() called
        if (l == null) {
        	try {
        		init(new FileInputStream(filename));
        	} catch (IOException e) {
        		init(null);
        	}
        }
        return l;        
    }    
    
    /**
     * Get the created Dumpers
     * @return the dumpers
     */
    public static final ArrayList<Dumper> getDumpers() {
        return dumpers;
    }

    /**
     * Private method for init our Log
     * @param is The InputStream of our properties file
     */
    private static void init(InputStream is) {
    	
    	// registering our hook....
    	Runtime.getRuntime().addShutdownHook(new ShutdownHook());
    	
    	// set mode...
    	status = LogFactory.status.RUNNING;
    	
        try {
        	// basic check...
        	if (is == null) {
        		throw new LightweightlogInitializationException("Properties null, using default one");
        	}
        	
            // load property file
            Properties fp = new Properties();
            try {
            	fp.load(is);
            } catch(IOException e) {
            	throw new LightweightlogInitializationException(e.getCause());
            }
            
            // then we init in turn all our necessary objects...
            initLog(fp);
            initDumper(fp);
            initRegister(fp, getDumpers());
            initQueue(fp);
            
            // we check if our Queue implements NotCumulable
            // which means we drop our entry directly to Dumper
            // and we do not need to start QueueDumper....
            if (q.isCumulable()) {
                initQueueDumper(fp);
                Thread t = new Thread(getQueueDumper());
                t.setName("QueueDumper");
                t.start();
            }
            // set status
            status = LogFactory.status.RUNNING;
           
            // just a note...
            l.info("------------------------------------------");
            l.info("Lightweightlog initialized properly");
            l.info("------------------------------------------");
            
            // useful information...
            l.debug(getRegister());
            l.debug(getQueueDumper());
            l.debug(getQueue());
            
        } catch (LightweightlogInitializationException e) {
            // Log didn't initialize properly...
            // so we first clean any existing object
        	if (getDumpers() !=null) {
        		for (Dumper x: getDumpers())
        			if (x != null) x.kill();            
        	}
            
            if (r !=null) {
                r.shutdown();
                r=null;
            }
            
            if (qd !=null) {
                qd.shutdown();
                qd=null;
            }
            
            if (l!= null) {
            	l.shutdown();
            	l=null;
            }
            
            // then we build a default log from default configuration file
            InputStream is2 = null;
            Properties p_safe = new Properties();
            try {
                // loading 'filename' prop file
            	// this exists for sure....it's in the package
                is2 = LogFactory.class.getClassLoader().getResourceAsStream(PROP_DEFAULT);
                p_safe.load(is2);
            }  catch(IOException ioe) {}
            finally {
                try {
                    if (is2 != null) {is2.close();}
                } catch (IOException ex){}
            }   
            // init each element...
            initLog(p_safe);
            initDumper(p_safe);
            initRegister(p_safe,getDumpers());
            initQueue(p_safe);

            // set mode...
            status = LogFactory.status.RUNNING;
            
            // at this point we should have our log...
            l.info("------------------------------------------");
            l.info("Lightweightlog initialized");
            l.info("Using default configuration");
            l.info("------------------------------------------");
            // now we can also store our previous error...
            l.fatal(e.getMessage());
            
        } finally {
            try {
                if (is != null) {
                    is.close();
                }
            } catch (IOException ex) {
                // we are doing nothing
            }
        }   
    }    
    
    
    /**
     * Initialize all our dumper
     * @param fp Custom set of properties
     * @throws DumperInitializationException if at least a Dumper with <I>killonfailure</I> fail to initialize
     */
    private static void initDumper(Properties fp) throws DumperInitializationException {
    	
    	// basic check...
    	if (getDumpers() !=null) {
    		l.warn("Cannot instantiate twice");
    		return;
    	}    		
        
        try {
            // start with looking for dumper.name=.....,....,.....
            String[] declaredDumpers = Pattern.compile(",").split(fp.getProperty(Constant.PROP_ROOT + "name"));
            // nothing to say...if dumper are found
            // we store them into register
            // if any error occur initializing the dumper we run in emergency mode
            if (declaredDumpers.length > 0) {
                // uhm...some dumper found, let's have a look
                // we need to store them on register
                dumpers =  new ArrayList<Dumper>(declaredDumpers.length);
                
                // looping trough them...
                dumperloop:
                for (String name : declaredDumpers) {
                    // dumper holder...
                    Dumper dumper = null;
                    try {
                        // classname to look for...
                        String classname = fp.getProperty(PROP_ROOT + name + ".classname");

                        // load class and its corresponding constructor...
                        Class<?> c = Class.forName(classname.trim());

                        // we need to let the dumper know what is its name
                        // we add it to the set of properties, but the dumper
                        // after having got this value it's also removing...
                        fp.setProperty("name", name);

                        // use reflection for the rest...all successful dumper goes to array
                        Constructor<?> cst = c.getConstructor(Properties.class);
                        dumper = (Dumper) cst.newInstance(new Object[]{fp});
                        dumpers.add(dumper);

                        // TO-DO serialization

                    } catch (RuntimeException e) {
                            throw new DumperInitializationException(e.getCause());
                    } catch (Exception e) {
                            throw new DumperInitializationException(e.getCause());
                    }
                }
            }
            
        } catch(Exception e) {
            throw new DumperInitializationException(e);
        }
    }
         
    /**
     * Initialize our log, if there is already a valid instance this method will just 'return'
     * @param p custom set of properties
     * @throws LogInitializationException if log fail to initialize
     */
    private static void initLog(Properties p) throws LogInitializationException {
        // we first check if we already have a
        // log available, if not we instantiate
        // and store it in variable 'l'
    	if (l !=null) {
    		l.warn("Cannot instantiate twice");    		
    		return;
    	} else {
            try {
            	// search for classname in property file...
                String classname = p.getProperty(Constant.PROP_ROOT + "log.classname", Constant.DEFAULT_LOG);
                // then...all that follows to get a valid Register with reflection...
                Class<?> c = Class.forName(classname.trim());
                Constructor<?> cst = c.getConstructor(Properties.class);
                l = (Log) cst.newInstance(new Object[] {p});
                
            } catch (RuntimeException e) {
                throw new LogInitializationException(e);
            } catch (Exception e) {
                throw new LogInitializationException(e);
            }
        }
    }
    
    
    /**
     * Initialize our Queue, if there is already a valid instance this method will just 'return'
     * @param p custom set of properties
     * @throws it.wzm.lightweightlog.QueueInitializationException if queue fail to initialize
     */
    @SuppressWarnings("unchecked")
    private static void initQueue(Properties p) throws QueueInitializationException {
        
        // we first check if we already have a
        // queue available, if not we instantiate
        // and store it in variable 'q'
    	// basic check...
    	if (getQueue() !=null) {
    		l.warn("Cannot instantiate twice");
    		return;
    	} else {
            try {
                // search for classname in property file...
                String classname = p.getProperty(Constant.PROP_ROOT + "queue.classname", Constant.DEFAULT_QUEUE);
                // then...all that follows to get a valid Register with reflection...
                
                // deciding which instance of Queue we want to create
                Class<?> c = Class.forName(classname.trim());
                Constructor<?> cst = c.getConstructor(Properties.class);
                q = (Queue<? extends Entry>) cst.newInstance(new Object[] {p});
                
            } catch (RuntimeException e) {
                throw new QueueInitializationException(e);
            } catch (Exception e) {
                throw new QueueInitializationException(e);
            }
        }
    }
    
    /**
     * Return current queue size
     * @return int -1 if null otherwise the actual Queue size
     */
    public static int getQueueSize() {
        // -1 means null...
        if (q!=null)
            return q.size();
        else
            return -1;
    }
    
    
    /**
     * Initialize our register, if there is already a valid instance this method will just 'return'
     * @param p custom set of properties
     * @param d Our array of Dumper
     * @throws it.wzm.lightweightlog.RegisterInitializationException if register fail to initialize
     */
    @SuppressWarnings("unchecked")
    private static void initRegister(Properties p, ArrayList<? extends Dumper> d) throws RegisterInitializationException {
        // check if we already have it...
    	if (getRegister() !=null) {
    		l.warn("Cannot instantiate twice");
    		return;
    	} else {
            // search for classname in property file...
            String classname = p.getProperty(Constant.PROP_ROOT + "register.classname", Constant.DEFAULT_REGISTER);
            // then...all that follows to get a valid Register with reflection...
            try {
                Class<?> c = Class.forName(classname.trim());
                Constructor<?> cst = c.getConstructor(Properties.class,ArrayList.class);
                // store register...
                r = (Register<? extends Dumper>) cst.newInstance(new Object[] {p,d});
                
            } catch (RuntimeException e) {e.printStackTrace();
            	throw new RegisterInitializationException(e);
            } catch (Exception e) {e.printStackTrace();
            	throw new RegisterInitializationException(e);
            }
        }
    }
    
    /**
     * Getter for register size
     * @return int -1 if null otherwise the actual Register size
     */
    public static int getRegisterSize() {
        // -1 means null
        if (r!=null)
            return r.size();
        else
            return -1;
    } 
    
    /**
     * Initialize our QueueDumper, if there is already a valid instance this method will just 'return'
     * @param p custom set of properties
     * @throws it.wzm.lightweightlog.QueueDumperInitializationException if fail to initialize
     */
    @SuppressWarnings("unchecked")
    private static void initQueueDumper(Properties p) throws QueueDumperInitializationException {
        
        // we first check if we already have a
        // queue dumper available, if not we instantiate
        // and store it in variable 'q'
    	if (getQueueDumper() !=null) {
    		l.warn("Cannot instantiate twice");
    		return;
    	} else {
            try {
                // search for classname in property file...
                String classname = p.getProperty(PROP_ROOT + "queuedumper.classname", DEFAULT_QUEUE_DUMPER);

                // then...all that follows to get a valid Register with reflection...
                Class<?> c = Class.forName(classname.trim());
                Constructor<?> cst = c.getConstructor(Properties.class);
                qd = (QueueDumper) cst.newInstance(new Object[] {p});
                
            } catch (RuntimeException e) {
                throw new QueueDumperInitializationException(e);
            } catch (Exception e) {
                throw new QueueDumperInitializationException(e);
            }
        }
    }
    
    /**
     * Getter for Queue
     * @return Queue
     */
    static Queue getQueue() {
        return q;
    }
    
    /**
     * Getter for Register
     * @return Register
     */
    static Register<? extends Dumper> getRegister() {
        return r;
    }
    
    /**
     * Getter for QueueDumper
     * @return QueueDumper
     */
    static QueueDumper getQueueDumper() {
        return qd;
    }
    
    /**
     * Free resources...
     */
    public static void shutdown() {    	
    	
        // just a note...
        l.info("------------------------------------------");
        l.info("Lightweightlog closed properly");
        l.info("------------------------------------------");
    	
        // shutdown QueueDumper
    	// it is not always running...
    	if (getQueueDumper() != null)
    		qd.shutdown();
    	
        // before shutdown we try to clear any entry we may have in log    	
    	if (!q.isEmpty()) {
    		LinkedList<? extends Entry> ll = q.retainAll();
    		r.dispatchToDumper(ll);
    	}
    	// now we close register and the rest...
    	r.shutdown();
    	r = null;     
        q = null;
        l = null;
    }
    
}

